/* this is the cup file for the mini language
 * at http://www.cs.drexel.edu/~jjohnson/2010-11/spring/cs550/lectures/lec2c.html *
 * created by Xu, 2/5/07
 * 
 * Modified by Mike Kopack for CS550, 2009 Spring Qtr.
 * Should be at the same level of completeness as the Lecture 2c
 * C++ version.
 */

import java.util.*;

class Expr {

    public Expr() {
    }

    public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) {
        return new Element(0);
    }
}



class Sequence{

    ArrayList<Expr> listElements;
    
    public Sequence(){
        listElements = new ArrayList<Expr>();
    }
    
    public void insertListElement (Expr listElement){
        listElements.add(listElement);
    }
    
    public void print(){
        int size = listElements.size();
        for(int i = 0; i < size; i++){
            System.out.print(listElements.get(i).toString());
            if(i != size-1)
                System.out.print(", ");
        }
    }
}

class Element extends Expr{
       
    Integer val;
    List_ lst;
    boolean isList;
    
    public Element(int v){
        val =new Integer(v);
        isList = false;
    }
    
    public Element(Integer v){
        val = v;
    }
    public Element (List_ l){
        lst = l;
        isList = true;
    }
    
    public int value(){
        
        return val;
    }
    
    public List_ getList(){
        return lst;
    }
    
        
    public void print(){
        if(isList)
            lst.print();
        else
            System.out.println(val.toString());
    }
}




class List_ extends Expr{
    
    //private List<Element> list;
    private Sequence seq;
    
    public List_(){
        seq = null;
        //list = new ArrayList<Element>();
    }
    
    public void insertSequence (Sequence sequence){
        seq = sequence;
    }
    
    public void print(){
        System.out.print("[");
        if(seq != null)
            seq.print();
        System.out.println("]");
    }

    public Sequence getSequence(){
        return seq;
    }
    
    public int getListSize(){
        return seq.listElements.size();
    }
    
    public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) {
        return new Element(this);
    }
}
    class Concat extends Expr{
        
        private Expr l1,l2;
        
        public Concat(Expr e1, Expr e2){
            l1 = e1;
            l2 = e2;
        }
        
        public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var){
            
            List_ list1, list2;
            //They both need to be lists
            if(!l1.eval(nametable, functiontable, var).isList || !l2.eval(nametable, functiontable, var).isList){
                System.out.println("-error--");
                System.exit(1);
            }
                
            list1 = l1.eval(nametable, functiontable, var).getList();
            list2 = l2.eval(nametable, functiontable, var).getList();
            
            List_ finalList = new List_();
            finalList.insertSequence(list2.getSequence());
            int size = list1.getListSize();
            //in theory should put the sequence from list 2 into a new list then start from the back of
            //list1 and put each of those elements into the front of finalList
            for(int i = size-1; i>=0; i--){
                finalList.getSequence().insertListElement(list1.getSequence().listElements.get(i));
            }
            
            
            return new Element(finalList);
            
            
        }
    }//end class Concat
    
    class Cons extends Expr{
        Expr e, L;
        
        public Cons(Expr e1, Expr L1){
            e = e1;
            L = L1;
        }
        
        public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var){
           List_ list, list2;
           
           list = L.eval(nametable, functiontable, var).getList();
           List_ finalList = new List_();
           finalList.insertSequence(list.getSequence());
           //if the e term is a list, turns into a concat
           if(e.eval(nametable, functiontable, var).isList){
           
               list2 = e.eval(nametable, functiontable, var).getList();
               for(int i = list.getListSize()-1; i>=0; i-- ){
                   finalList.getSequence().insertListElement(list2.getSequence().listElements.get(i));
               }
           }
           else{
               finalList.getSequence().insertListElement(e);
           }
            
        return new Element(finalList);    
        }
        
        }//end cons
        
        class Car extends Expr{
            Expr e;
            
            public Car(Expr e1){
                e = e1;
            }
        
            public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var){
                List_ list;
            
                list = e.eval(nametable, functiontable, var).getList();
                list.print();
                List_ finalList = new List_();
               Sequence s = new Sequence();
                s.insertListElement(list.getSequence().listElements.get(0));
               finalList.insertSequence(s);            
            
            
            return new Element(finalList);
        }

        
        }//end Car
        
        class Cdr extends Expr{
            
            Expr e;
            
            public Cdr(Expr e1){
                e = e1;
            }
        
            public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var){
                List_ list;
                
                list = e.eval(nametable, functiontable, var).getList();
                List_ finalList = new List_();
            //in theory starts from second index in list(1) and puts all into the new list
                for(int i = 1; i < list.getListSize(); i++){
                    finalList.getSequence().insertListElement(list.getSequence().listElements.get(i));
                }
               return new Element(finalList);
            }
        }
            
        class Nullp extends Expr{
            
            Expr list;
            
            public Nullp(Expr e){
                list = e;
            }
            
            public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var){
               List_ l;
               l = list.eval(nametable, functiontable, var).getList();
               
               if(l.getSequence() == null)
                   return new Element(1);
               else
                   return new Element(0);
            }

            
        }
        
        class Intp extends Expr{
            
            Expr num;
            
            public Intp (Expr e){
                num = e;
            }
         
            public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var){
                if(!num.eval(nametable, functiontable, var).isList)
                    return new Element(1);
                else
                    return new Element(0);
            }

        
        }//end intp

        
        class Listp extends Expr{
            
            Expr list;
            
            public Listp ( Expr e){
                list = e;
            }
        
            public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var){
                
                if(list.eval(nametable, functiontable, var).isList)
                    return new Element(1);
                else
                    return new Element(0);
            }

        
        }//end listp

    
    


class Ident extends Expr {

    private String name;

    public Ident(String s) {
        name = s;
    }

    public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) {
        return nametable.get(name);
    }
}

class Number extends Expr {

    private Integer value;

    public Number(int n) {
        value = new Integer(n);
    }

    public Number(Integer n) {
        value = n;
    }

    public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) {
        return new Element(value);
    }
}

class Times extends Expr {

    private Expr expr1,  expr2;

    public Times(Expr op1, Expr op2) {
        expr1 = op1;
        expr2 = op2;
    }

    public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) {
        return new Element(expr1.eval(nametable, functiontable, var).value() * expr2.eval(nametable, functiontable, var).value());
    }
}

class Plus extends Expr {

    private Expr expr1,  expr2;

    public Plus(Expr op1, Expr op2) {
        expr1 = op1;
        expr2 = op2;
    }

    public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) {
        return new Element(expr1.eval(nametable, functiontable, var).value() + expr2.eval(nametable, functiontable, var).value());
    }
}

class Minus extends Expr {

    private Expr expr1,  expr2;

    public Minus(Expr op1, Expr op2) {
        expr1 = op1;
        expr2 = op2;
    }

    public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) {
        return new Element(expr1.eval(nametable, functiontable, var).value() - expr2.eval(nametable, functiontable, var).value());
    }
}

//added for 2c
class FunctionCall extends Expr {

    private String funcid;
    private ExpressionList explist;

    public FunctionCall(String id, ExpressionList el) {
        funcid = id;
        explist = el;
    }
   //debateable
    public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) {
        return new Element(functiontable.get(funcid).apply(nametable, functiontable, var, explist));
    }
}

abstract class Statement {

    public Statement() {
    }

    public void eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) throws Exception {
    }
}

// added for 2c
class DefineStatement extends Statement {

    private String name;
    private Proc proc;
    private ParamList paramlist;
    private StatementList statementlist;

    public DefineStatement(String id, Proc process) {
        name = id;
        proc = process;
    }

    public void eval(HashMap<String, Element> nametable, HashMap<String, Proc> functable, LinkedList var) {
        // get the named proc object from the function table.
        //System.out.println("Adding Process:"+name+" to Functiontable");
        functable.put(name, proc);
    }
}

class ReturnStatement extends Statement {

    private Expr expr;

    public ReturnStatement(Expr e) {
        expr = e;
    }

    public void eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) throws Exception {
        //Java can't throw exceptions of numbers, so we'll convert it to a string
        //and then on the other end we'll reconvert back to Integer..
        throw new Exception(Integer.toString(expr.eval(nametable, functiontable, var).value()));
    }
}

class AssignStatement extends Statement {

    private String name;
    private Expr expr;

    public AssignStatement(String id, Expr e) {
        name = id;
        expr = e;
    }

    public void eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) {
        /* add name to the statementlist of variable names */
        if (!var.contains(name)) {
            var.add(name);
        //insert the variable with the specified name into the table with the 
        // evaluated result (which must be an integer
        }
        nametable.put(name, expr.eval(nametable, functiontable, var));
    }
}

class IfStatement extends Statement {

    private Expr expr;
    private StatementList stmtlist1,  stmtlist2;

    public IfStatement(Expr e, StatementList list1, StatementList list2) {
        expr = e;
        stmtlist1 = list1;
        stmtlist2 = list2;
    }

    public IfStatement(Expr e, StatementList list) {
        expr = e;
        stmtlist1 = list;
    }

    public void eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) throws Exception {
        if (expr.eval(nametable, functiontable, var).value() > 0) {
            stmtlist1.eval(nametable, functiontable, var);
        } else {
            stmtlist2.eval(nametable, functiontable, var);
        }
    }
}

class WhileStatement extends Statement {

    private Expr expr;
    private StatementList stmtlist;

    public WhileStatement(Expr e, StatementList list) {
        expr = e;
        stmtlist = list;
    }

    public void eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) throws Exception {
        while (expr.eval(nametable, functiontable, var).value() > 0) {
            stmtlist.eval(nametable, functiontable, var);
        }
    }
}

class RepeatStatement extends Statement {

    private Expr expr;
    private StatementList sl;

    public RepeatStatement(StatementList list, Expr e) {
        expr = e;
        sl = list;
    }

    public void eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) throws Exception {
        do {
            sl.eval(nametable, functiontable, var);
        } while (expr.eval(nametable, functiontable, var).value() > 0);

    }
}

//added for 2c
class ParamList {

    private List<String> parameterlist;

    public ParamList(String name) {
        parameterlist = new LinkedList<String>();
        parameterlist.add(name);
    }

    public ParamList(String name, ParamList parlist) {
        parameterlist = parlist.getParamList();
        parameterlist.add(name);
    }

    public List<String> getParamList() {
        return parameterlist;
    }
}

// Added for 2c
class ExpressionList {

    private LinkedList<Expr> list;

    public ExpressionList(Expr ex) {
        list = new LinkedList<Expr>();
        list.add(ex);
    }

    public ExpressionList(Expr ex, ExpressionList el) {
        list = new LinkedList<Expr>();
        //we need ot add the expression to the front of the list
        list.add(0, ex);

    }

    public List<Expr> getExpressions() {
        return list;
    }
}

class StatementList {

    private LinkedList<Statement> statementlist;

    public StatementList(Statement statement) {
        statementlist = new LinkedList<Statement>();
        statementlist.add(statement);
    }

    public LinkedList<Statement> getStatements() {
        return statementlist;
    }

    public void eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) throws Exception {


        for (Statement stmt : statementlist) {
            stmt.eval(nametable, functiontable, var);

        }
    }

    public void insert(Statement s) {
        // we need to add it to the front of the list
        statementlist.add(0, s);
    }
}

class Proc {

    private ParamList parameterlist;
    private StatementList stmtlist;

    public Proc(ParamList pl, StatementList sl) {
        parameterlist = pl;
        stmtlist = sl;
    }

    public int apply(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var, ExpressionList expressionlist) {
        //System.out.println("Executing Proceedure");
        HashMap<String, Element> newnametable = new HashMap<String, Element>();

        // bind parameters in new name table
        // we need to get the underlying List structure that the ParamList uses...
        Iterator<String> p = parameterlist.getParamList().iterator();
        Iterator<Expr> e = expressionlist.getExpressions().iterator();

        if (parameterlist.getParamList().size() != expressionlist.getExpressions().size()) {
            System.out.println("Param count does not match");
            System.exit(1);
        }
        while (p.hasNext() && e.hasNext()) {

            // assign the evaluation of the expression to the parameter name.
            newnametable.put(p.next(), e.next().eval(nametable, functiontable, var));
        //System.out.println("Loading Nametable for procedure with: "+p+" = "+nametable.get(p));

        }
        // evaluate function body using new name table and 
        // old function table
        // eval statement list and catch return
        //System.out.println("Beginning Proceedure Execution..");
        try {
            stmtlist.eval(newnametable, functiontable, var);
        } catch (Exception result) {
            // Note, the result shold contain the proceedure's return value as a String
            //System.out.println("return value = "+result.getMessage());
            return Integer.parseInt(result.getMessage());
        }
        System.out.println("Error:  no return value");
        System.exit(1);
        // need this or the compiler will complain, but should never
        // reach this...
        return 0;
    }
}

class Program {

    private StatementList stmtlist;

    public Program(StatementList list) {
        stmtlist = list;
    }

    public void eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) {
        try {
            stmtlist.eval(nametable, functiontable, var);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    public void dump(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) {
        //System.out.println(hm.values());
        System.out.println("Dumping out all the variables...");
        if (nametable != null) {
            for (String name : nametable.keySet()) {
                   //Prints addresses. i tack on a .value() to print out numbers and .getList() gives me an address
                System.out.println(name + "=" + nametable.get(name));
            }
        }
        if (functiontable != null) {
            for (String name : functiontable.keySet()) {
                System.out.println("Function: " + name + " defined...");
            }
        }
    }
}

